using System;
using MapWindowDocking.Mapinfo;
using MapWindowDocking.Mapinfo.Enums;
using MapInfo.MiPro.Interop;
using System.Globalization;

namespace MapWindowDocking
{
	/// <summary>
	/// Repersents a Mapinfo Window object. 
    /// Can be used to query and set values for the window in Mapinfo.
	/// </summary>
	public class Window
	{
        public event Action<Window> WindowChanged;
        private readonly MapinfoWrapper mapinfo;

        public Window(int windowID, MapinfoWrapper mapinfo)
		{
			this.ID = windowID;
            this.mapinfo = mapinfo;
            this.mapinfo.innermapinfo.WindowContentsChanged += MapInfoApplication_WindowContentsChanged;
		}

        /// <summary>
        /// The ID of the current window.
        /// </summary>
        public int ID { get; private set; }

        /// <summary>
        /// Gets or sets the Center X point for the window.
        /// </summary>
        public double CenterX
        {
            get
            {
                string strvalue = this.mapinfo.Eval(String.Format("MapperInfo({0},3)", this.ID));
                double centerx;
                if (double.TryParse(strvalue, System.Globalization.NumberStyles.Number, Globals._usNumberFormat, out centerx))
                    return centerx;
                else
                    throw new MapBasicException(string.Format("Invalid return type from MapBasic. Expected double. Return value: {0}", strvalue));
            }
            set 
            {
                this.UpdateCenters(value, this.CenterY);
            }
        }

        /// <summary>
        /// Gets or sets the Center Y point for the window.
        /// </summary>
        public double CenterY
        {
            get
            {
                string strvalue = this.mapinfo.Eval(String.Format("MapperInfo({0},4)", this.ID));
                double centery;
                if (double.TryParse(strvalue, System.Globalization.NumberStyles.Number, Globals._usNumberFormat, out centery))
                    return centery;
                else
                    throw new MapBasicException(string.Format("Invalid return type from MapBasic. Expected double. Return value: {0}", strvalue));
            }
            set
            {
                this.UpdateCenters(this.CenterX, value);
            }
        }

        /// <summary>
        /// Gets or sets the zoom width for the window.
        /// </summary>
        public double Zoom
        {
            get
            {
                string strvalue = this.mapinfo.Eval(String.Format("MapperInfo({0},1)", this.ID));
                double zoom;
                if (double.TryParse(strvalue, System.Globalization.NumberStyles.Number, Globals._usNumberFormat, out zoom))
                    return zoom;
                else
                    throw new MapBasicException(string.Format("Invalid return type from MapBasic. Expected double. Return value: {0}", strvalue));
            }
            set
            {
                this.UpdateZoom(value, this.ZoomUnit);
            }
        }

        /// <summary>
        /// Gets the zoom width for the window.
        /// </summary>
        public string ZoomUnit
        {
            get
            {
                return this.mapinfo.Eval(String.Format("MapperInfo({0},12)", this.ID));
            }
        }

        private IntPtr handle;
        /// <summary>
        /// Gets the handle to the window.
        /// </summary>
		public IntPtr Handle 
        {
            get 
            {
                if (this.handle == IntPtr.Zero)
                {
                    string strwindowHWND = this.mapinfo.Eval(string.Format("WindowInfo({0},{1})", this.ID, (int)WindowInfo.Wnd));
                    this.handle = new IntPtr(Convert.ToInt32(strwindowHWND));
                }
                return this.handle;
            } 
       }
		
        private WindowTypes type;
        /// <summary>
        /// Gets the window type.
        /// </summary>
        public WindowTypes Type
        {
            get 
            {
                if (this.type == 0)
                {
                    string frontwindowtype = this.mapinfo.Eval(String.Format("WindowInfo({0},{1})", this.ID, (int)WindowInfo.Type));
                    this.type = (WindowTypes)(Convert.ToInt32(frontwindowtype));
                }
                return this.type;
            }
        }

        /// <summary>
        /// Gets the coordsys for the window.
        /// </summary>
        public string CoordSys
        {
            get
            {
                return this.mapinfo.Eval(String.Format("MapperInfo({0},{1})", this.ID, (int)MapperInfoTypes.Coordsys_clause_with_bounds));
            }
        }

        /// <summary>
        /// Updates the current zoom of the map.
        /// </summary>
        /// <param name="zoom">The zoom amount.</param>
        /// <param name="unit">The distance unit eg m,km etc</param>
        public void UpdateZoom(double zoom, string unit)
        {
            string command = string.Format("Set Map Window {0} Zoom {1} Units \"{2}\"", this.ID, zoom, unit);
            this.mapinfo.Do(command);
        }

        /// <summary>
        /// Updates the centres of the map.
        /// The X and Y properties also update the map.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void UpdateCenters(double x, double y)
        {
            this.mapinfo.Do("Set Map Window " + this.ID +
                                    " Center ( " + String.Format(Globals._usNumberFormat, "{0}", x) + " , " + String.Format(Globals._usNumberFormat, "{0}", y) + ")");
        }

        private bool callbacksync;


        void MapInfoApplication_WindowContentsChanged(object sender, WindowContentsChangedEventArgs e)
        {
            //Check to see if the window change event was fired for this window object.
            if (this.ID == e.Id)
            {
                // Because for some reason the Mapbasic coordinate system gets reset
                // back to lat long the first time a callback is
                // made we have to sync the coordsys again.

                // We also check to see if the window focus has changed because the Mapbasic session coordsys may
                // be different to the current window.
                if (!callbacksync | Class1.WindowFocusHasChanged)
                {
                    this.mapinfo.SessionCoordSys = this.CoordSys;
                    this.mapinfo.SessiongDistanceUnits = this.ZoomUnit;
                    callbacksync = true;
                    Class1.WindowFocusHasChanged = false;
                }
                // Raises the changed event for this window letting subscribers know that 
                // the contents has changed.
                this.RaiseChangedEvent();

            }
        }

        /// <summary>
        /// Raises the public WindowChanged event to let listeners know that something in the window has changed.
        /// </summary>
        private void RaiseChangedEvent()
        {
            if (this.WindowChanged != null)
            {
                this.WindowChanged(this); 
            }
        }
	}
}
